#include "StdAfx.h"
#include "Terrain.h"


Terrain::Terrain(void)
{
}

Terrain::~Terrain(void)
{
}


float Terrain::filterEach(int x, int y)
{
	float res = 0;
	float count = 1;
	for (int i = x - 1; i < x + 1; ++i)
	{
		for (int j = y - 1; j < y + 1; ++j)
		{
			int k  = j * vertCols + i;
			if (k >= 0 && k < model.num_vertices)
			{
				res += model.vertices[k].pos.y;
				count++;
			}		
		}
	}
	return res / count;
}

void Terrain::filterAll()
{
	GLfloat* tmp_height = new GLfloat[model.num_vertices];

}

int Terrain::Load (Vector3 center, char* height_map, char* blend_map, char* tex_low_tga, char* tex_med_tga, char* tex_high_tga, char* tex_normal_map)
{
	int height, width, bpp;
	// Generate map
	//=========	Generate triangles grid
	 vertRows = 128;
	 vertCols = 128;
	float dx = 10.0f, dz = 10.0f;
	GenerateTriangleGrid(vertRows, vertCols, dx, dz, center);
	LoadRAW(vertCols, vertRows, height_map, 1.0f, -100.0f);
	GenerateNormals();

	// Assume 24bit
	glGenTextures(5, id);
	char* tex[] = {blend_map, tex_low_tga, tex_med_tga, tex_high_tga, tex_normal_map};
	for(int i = 0; i < 5; ++i)
	{
		
		glBindTexture(GL_TEXTURE_2D, id[i]);
		char* imgData = LoadTGA(tex[i], &width, &height, &bpp);
		//
		if (bpp == 24)
			//RGB
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, imgData);
		else
			//RGBA
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imgData);
		////filter for minification
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
		////filter for magnification
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glGenerateMipmap(GL_TEXTURE_2D);

		delete[] imgData;
	}


	glBindTexture(GL_TEXTURE_2D, 0);
	// Buffer vertices
	glGenBuffers(1, &vbo);
	glBindBuffer(GL_ARRAY_BUFFER, vbo);
	glBufferData(GL_ARRAY_BUFFER, model.num_vertices * sizeof(Vertex), model.vertices, GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	//

	glGenBuffers(1, &ibo);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, model.num_indices * sizeof(GLushort), model.indices, GL_STATIC_DRAW);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

	myShaders.Init("../Resources/Shaders/TerrainShaderVS.vs", "../Resources/Shaders/TerrainShaderFS.fs");

	
	myShaders.getUniformLocation("u_light_position", lightPositionUniform);
	myShaders.getUniformLocation("u_normalmap", normalMapUniform);
	myShaders.getUniformLocation("u_light_color", lightColorUniform);
	myShaders.getAttributeLocation("a_tangentL", tangentAttribute);
	myShaders.getAttributeLocation("a_bitangentL", bitangentAttribute);
	return LOAD_SUCCEEDED;
}


void Terrain::Draw( ESContext *esContext, Camera * camera)
{
	glUseProgram(myShaders.program);
	glBindBuffer(GL_ARRAY_BUFFER,vbo);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);

	// Vertex
	glEnableVertexAttribArray(myShaders.positionAttribute);
	glVertexAttribPointer(myShaders.positionAttribute, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);

	// Bind norm to Shaders
	glEnableVertexAttribArray(myShaders.normalAttribute);
	glVertexAttribPointer(myShaders.normalAttribute, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (char*) 0 + 2 * sizeof(Vector3) + sizeof(Vector2) + sizeof(Vector4));

	// Bind tangent to Shaders
	glEnableVertexAttribArray(tangentAttribute);
	glVertexAttribPointer(tangentAttribute, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (char*)0 + sizeof(Vector3) + sizeof(Vector4) + sizeof(Vector2));

	// Bind bitangent to Shaders
	glEnableVertexAttribArray(bitangentAttribute);
	glVertexAttribPointer(bitangentAttribute, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (char*)0 + sizeof(Vector3) + sizeof(Vector4) + sizeof(Vector2) + sizeof(Vector3));

	glEnableVertexAttribArray(myShaders.uvAttribute);
	glVertexAttribPointer(myShaders.uvAttribute, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (char*)0 + sizeof(Vector3) + sizeof(Vector4));
	// MVP
	Matrix mvp =  camera->getViewMatrix() * camera->getProjectionMatrix();
	glUniformMatrix4fv(myShaders.matMVPUniform, 1, GL_FALSE, &(mvp.m[0][0]));


	// Texture

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, id[0]);
	glUniform1i(myShaders.blendUniform, 0);

	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, id[1]);
	glUniform1i(myShaders.texLowUniform, 1);

	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_2D, id[2]);
	glUniform1i(myShaders.texMedUniform, 2);

	glActiveTexture(GL_TEXTURE3);
	glBindTexture(GL_TEXTURE_2D, id[3]);
	glUniform1i(myShaders.texHighUniform, 3);

	glActiveTexture(GL_TEXTURE3);
	glBindTexture(GL_TEXTURE_2D, id[3]);
	glUniform1i(myShaders.texHighUniform, 3);

	glActiveTexture(GL_TEXTURE4);
	glBindTexture(GL_TEXTURE_2D, id[4]);
	glUniform1i(normalMapUniform, 4);
	
	// Bind light position
	glUniform3f(lightPositionUniform, lightPosition.x, lightPosition.y, lightPosition.z);
	
	// Bind light color
	glUniform3f(lightColorUniform, lightColor.x, lightColor.y, lightColor.z);


	// Bind World Matrix
	glUniformMatrix4fv(myShaders.matWorldUniform, 1, GL_FALSE, &(getWorldMatrix().m[0][0]));


	// bind camera pos
	
	Vector3 pos = camera->getPosition();
	glUniform3f(myShaders.cameraPositionUniform, pos.x, pos.y, pos.z);
	// Pass Index
	int size;
	glGetBufferParameteriv(GL_ELEMENT_ARRAY_BUFFER, GL_BUFFER_SIZE, &size);

	// Draw
	glDrawElements(GL_TRIANGLES, size / sizeof(GLushort), GL_UNSIGNED_SHORT, 0);
	//glDrawElements(GL_LINE_LOOP, size / sizeof(GLushort), GL_UNSIGNED_SHORT, 0);
	//glDrawArrays(GL_POINTS, 0, 128*128);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

void Terrain::Update ( ESContext *esContext, float deltaTime)
{}

void Terrain::GenerateTriangleGrid(int vertRows, int vertCols, float dx, float dz,
		const Vector3 center)
{
	model.num_vertices = vertRows * vertCols;
	int cellRows = vertRows - 1;
	int cellCols = vertCols - 1;
	int numTriangles = cellRows * cellCols * 2;

	width = (float) cellRows * dx;
	height = (float) cellRows * dz;
	
	model.vertices = new Vertex[model.num_vertices];
	float xOffset = -width * 0.5f;
	float zOffset = -height * 0.5f;

	int k = 0;
	for (float i = 0; i < vertRows; ++i)
	{
		for (float j = 0; j < vertCols; ++j)
		{
			float x = j * dx;
			model.vertices[k].pos.x = x + xOffset ;
			float y = i * dz;
			model.vertices[k].pos.z = y + zOffset;
			model.vertices[k].pos.y = 0.0f;

			model.vertices[k].uv.x = x / width;
			model.vertices[k].uv.y = y / height;
			
			Matrix m;
			m.SetTranslation(center.x, center.y, center.z);
			model.vertices[k].pos = Vector4(model.vertices[k].pos, 1) * m;

			++k;		
		}
	}

	// Calculate indices
	model.num_indices = numTriangles * 3;
	model.indices = new GLushort[model.num_indices];
	k = 0;
	for (int i = 0; i < cellRows; ++i)
	{
		for (int j = 0; j < cellCols; ++j)
		{
			model.indices[k] =		 i	   * vertCols + j;
			model.indices[k + 1] =	 i	   * vertCols + j + 1;
			model.indices[k + 2] = (i + 1) * vertCols + j;

			model.indices[k + 3] = (i + 1)  * vertCols + j;
			model.indices[k + 4] =	  i		* vertCols + j + 1;
			model.indices[k + 5] = (i + 1)  * vertCols + j + 1;

			k += 6;
		}
	}
}

int Terrain::LoadRAW(int nVertCols, int nVertRows, char* file_name, float height_scale, float height_offset)
{
	GLubyte* input = new GLubyte[nVertCols * nVertRows];
	ifstream file;
	file.open(file_name, ios::binary);
	if (!file)
		return LOAD_FAILED;
	file.read((char*)&input[0], (streamsize) nVertCols * nVertRows);
	int size = file.tellg();

	for (int k = 0; k < size; k++)
	{
			model.vertices[k].pos.y = (float) input[k] * height_scale + height_offset;
	}

	return 0;
}


void Terrain::GenerateNormals()
{
	for (int i = 0; i < model.num_indices / 3; i++)
	{
		// Get data
		GLushort* indices = model.indices;
		GLushort idxA = indices[3 * i];
		GLushort idxB = indices[3 * i + 1];
		GLushort idxC = indices[3 * i + 2];
		Vertex* A,* B,* C;
		A = &model.vertices[idxA];
		B = &model.vertices[idxB];
		C = &model.vertices[idxC];
		Vector3 pointA = (*A).pos;
		Vector3 pointB = (*B).pos;
		Vector3 pointC = (*C).pos;
		
		// Calculate triangle edge vectors
		Vector3 AB = pointB - pointA;
		Vector3 AC = pointC - pointA;

		// Calculate face normal
		Vector3 face_norm = AC.Cross(AB);
		
		// Calculate vertex normal
		A->normal = (A->normal +=  face_norm).Normalize();
		B->normal = (B->normal +=  face_norm).Normalize();
		C->normal = (C->normal +=  face_norm).Normalize();
	}
}